rustdoc-json: Postcard output - #142642
Conversation
| pub mod postcard { | ||
|
|
||
| pub type Magic = [u8; 22]; | ||
| pub const MAGIC: Magic = *b"\x00\xFFRustdocJsonPostcard\xFF"; |
There was a problem hiding this comment.
A friend points out https://hackers.town/@zwol/114155807716413069, with advice on how to design a magic number.
There was a problem hiding this comment.
Having 'Json' in there seems perverse :)
There was a problem hiding this comment.
Sadly that link is now dead since they moved the server to masto.hackers.town and the posts no longer has the same ID, or it doesn't exists
There was a problem hiding this comment.
It got archived: https://archive.is/0QDhX. Repeated here for posterity.
another day, another binary file format with a badly designed magic number
not gonna call it out specifically but here are some RFC2119 MUSTs for magic number design:
- MUST be the very first N bytes in the file
- MUST be at least four bytes long, eight is better
- MUST include at least one byte with the high bit set
- MUST include a byte sequence that is invalid UTF-8
- SHOULD include a zero byte, but you can usually get away with having that be part of the overall version number that immediately follows the magic number (did I mention that you really SHOULD put an overall version number right after the magic number, unless you know and have documented exactly why it's not necessary, e.g. PNG?)
good examples: PNG, ELF
bad examples: GIF PE PDF
Here is a template. If you follow this template for your binary file format's magic number, you will be doing it better than a depressingly large number of senior software engineers.
First eight bytes of the file:
0xDC 0xDF X X x x (0x01 0x00 | 0x00 0x01)
0xDC 0xDF are bytes with the high bit set. Together with the next two bytes, they form a four-byte sequence that cannot appear in any valid ASCII, UTF-8, Corrected UTF-8, or UTF-16 (regardless of endianness) text document. This is not a perfectly bulletproof declaration that the file does not contain text, but it should be strong enough except maybe for formats like PDF that can't decide if they're structured text or binary.
X X x x: Four ASCII alphanumeric characters naming your file format. Make them clearly related to your recommended file name extension. I'm giving you four characters because we're running out of three-letter acronyms. If you don't need four characters, pad at the end with 0x1A (aka ^Z).The first two of these (the uppercase Xes) must not have their high bits set, lest the "this is not text" declaration be weakened. For the other two (lowercase xes), use of ASCII alphanumerics is just a strong recommendation.
0x01 0x00 or 0x00 0x01: This is to be understood as a 16-bit unsigned integer in your choice of little- or big-endian order. It serves three functions. In descending order of importance:
- It includes a zero byte, reinforcing the declaration that this is not a text file.
- It demonstrates which byte ordering will be used throughout the file. It does not matter which order you choose, but you need to consciously choose either big- or little-endian and then use that byte order consistently throughout the file. Yes, I have seen cases where people didn't do that.
- It's an escape hatch. If one day you discover that you need to alter the structure of the rest of the file in a totally incompatible way, and yet it is still meaningfully the same format, so you don't want to change the name characters, you can change the 0x01 to 0x02. We both hope that day will never come, but we both know it might.
|
Something @jamesmunns pointed out is that this means that reordering fields or enum variants in More broadly, we should think about where (if at all) postcard-schema fits into this. |
|
As a note, I'm working on iterating on
|
|
A possibly useful form for the file format could be: struct PostcardFile<T> {
key: Key,
schema: Option<Schema>,
data: T,
}I've considered "standardizing" this format a bit, maybe with a trailing CRC32. |
Awesome! It'd be great to not have
I think we definatly want to keep the magic number, so that consumers can tell if this file is rustdoc output at all, and a linear format version so they can tell if rustdoc is too old or too new for them if the schema's changed (vs a schema hash that only tells you that it's changed). Embedded the schema into the output itself is an interesting idea, I'll need to look more at it. But as long as both of these come after the magic number and linear format version, we should be fine to change them after the fact. |
|
☔ The latest upstream changes (presumably #143173) made this pull request unmergeable. Please resolve the merge conflicts. |
…oundwork, r=jieyouxu compiletest: pass rustdoc mode as param, rather than implicitly Spun out of #142642 In the future, I want the rustdoc-json test suite to invoke rustdoc twice, once with `--output-format=json`, and once with the (not yet implemented) `--output-format=postcard` flag. Doing that requires being able to explicitly tell the `.document()` function which format to use, rather then implicitly using json in the rustdoc-json suite, and HTML in all others. r? `@jieyouxu` CC `@jalil-salame`
…oundwork, r=jieyouxu compiletest: pass rustdoc mode as param, rather than implicitly Spun out of rust-lang/rust#142642 In the future, I want the rustdoc-json test suite to invoke rustdoc twice, once with `--output-format=json`, and once with the (not yet implemented) `--output-format=postcard` flag. Doing that requires being able to explicitly tell the `.document()` function which format to use, rather then implicitly using json in the rustdoc-json suite, and HTML in all others. r? `@jieyouxu` CC `@jalil-salame`
…oundwork, r=jieyouxu compiletest: pass rustdoc mode as param, rather than implicitly Spun out of rust-lang/rust#142642 In the future, I want the rustdoc-json test suite to invoke rustdoc twice, once with `--output-format=json`, and once with the (not yet implemented) `--output-format=postcard` flag. Doing that requires being able to explicitly tell the `.document()` function which format to use, rather then implicitly using json in the rustdoc-json suite, and HTML in all others. r? `@jieyouxu` CC `@jalil-salame`
…oundwork, r=jieyouxu compiletest: pass rustdoc mode as param, rather than implicitly Spun out of rust-lang/rust#142642 In the future, I want the rustdoc-json test suite to invoke rustdoc twice, once with `--output-format=json`, and once with the (not yet implemented) `--output-format=postcard` flag. Doing that requires being able to explicitly tell the `.document()` function which format to use, rather then implicitly using json in the rustdoc-json suite, and HTML in all others. r? `@jieyouxu` CC `@jalil-salame`
…letest-groundwork, r=jieyouxu compiletest: pass rustdoc mode as param, rather than implicitly Spun out of rust-lang#142642 In the future, I want the rustdoc-json test suite to invoke rustdoc twice, once with `--output-format=json`, and once with the (not yet implemented) `--output-format=postcard` flag. Doing that requires being able to explicitly tell the `.document()` function which format to use, rather then implicitly using json in the rustdoc-json suite, and HTML in all others. r? `@jieyouxu` CC `@jalil-salame`
…letest-groundwork, r=jieyouxu compiletest: pass rustdoc mode as param, rather than implicitly Spun out of rust-lang#142642 In the future, I want the rustdoc-json test suite to invoke rustdoc twice, once with `--output-format=json`, and once with the (not yet implemented) `--output-format=postcard` flag. Doing that requires being able to explicitly tell the `.document()` function which format to use, rather then implicitly using json in the rustdoc-json suite, and HTML in all others. r? `@jieyouxu` CC `@jalil-salame`
…oundwork, r=jieyouxu compiletest: pass rustdoc mode as param, rather than implicitly Spun out of rust-lang/rust#142642 In the future, I want the rustdoc-json test suite to invoke rustdoc twice, once with `--output-format=json`, and once with the (not yet implemented) `--output-format=postcard` flag. Doing that requires being able to explicitly tell the `.document()` function which format to use, rather then implicitly using json in the rustdoc-json suite, and HTML in all others. r? `@jieyouxu` CC `@jalil-salame`
|
Cargo could potentially benefit from an alternative format (#t-cargo > `cargo metadata` performance @ 💬). Would be good for us to coordinate on the format used, at least when it comes to stabilizing. |
…oundwork, r=jieyouxu compiletest: pass rustdoc mode as param, rather than implicitly Spun out of rust-lang/rust#142642 In the future, I want the rustdoc-json test suite to invoke rustdoc twice, once with `--output-format=json`, and once with the (not yet implemented) `--output-format=postcard` flag. Doing that requires being able to explicitly tell the `.document()` function which format to use, rather then implicitly using json in the rustdoc-json suite, and HTML in all others. r? `@jieyouxu` CC `@jalil-salame`
…oundwork, r=jieyouxu compiletest: pass rustdoc mode as param, rather than implicitly Spun out of rust-lang/rust#142642 In the future, I want the rustdoc-json test suite to invoke rustdoc twice, once with `--output-format=json`, and once with the (not yet implemented) `--output-format=postcard` flag. Doing that requires being able to explicitly tell the `.document()` function which format to use, rather then implicitly using json in the rustdoc-json suite, and HTML in all others. r? `@jieyouxu` CC `@jalil-salame`
82d6bfb to
f0b5215
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| IrOutputFormat::Postcard => "postcard", | ||
| }; | ||
|
|
||
| dcx.fatal(format!( |
There was a problem hiding this comment.
This should have a rustdoc-ui test.
| pub(crate) fn is_json_output(&self) -> bool { | ||
| self.output_format == OutputFormat::IrJson | ||
| // TODO: Rename this! | ||
| matches!(self.output_format, OutputFormat::Ir(_)) |
There was a problem hiding this comment.
This needs renaming, but I'm not sure what to. Open to suggestions.
| OutputFormat::Ir(IrOutputFormat::Json) | OutputFormat::CoverageJson => dcx.fatal(format!( | ||
| "the `--emit={typ}` flag is not supported with `--output-format=json`", | ||
| )), | ||
| OutputFormat::Ir(IrOutputFormat::Postcard) => dcx.fatal(format!("the `--emit={typ}` flag is not supported with `--output-format=postcard`")), |
There was a problem hiding this comment.
This should have a rustdoc-ui test.
|
These commits modify the If this was unintentional then you should revert the changes before this PR is merged. Some changes occurred in src/tools/compiletest cc @jieyouxu rustdoc-json-types is a public (although nightly-only) API. If possible, consider changing |
|
Finally ready for review: |
|
Failed to set assignee to
|
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
I think it looks good! Nice work!
Is there a chance we might ever want to bump the postcard and JSON version numbers separately? For example, if we introduced a structural change in how the postcard format is represented without changing the Rust-side types. Or do we say that we'll just bump the JSON format number then too, even though we didn't really need to?
| /// Postcard allows reading these field from a file one at a time. | ||
| /// It's reccomended to check the magic constant matches before reading the format version |
There was a problem hiding this comment.
minor typo fixes:
| /// Postcard allows reading these field from a file one at a time. | |
| /// It's reccomended to check the magic constant matches before reading the format version | |
| /// Postcard allows reading these fields from a file one at a time. | |
| /// It's recommended to check the magic constant matches before reading the format version |
## What `rustdoc --output-format=postcard` is like rustdoc-json, but using https://postcard.rs/ / https://docs.rs/postcard/1.1.1/ instead of JSON. ## Why JSON Size and speed isn't great. People [want](https://rust-lang.zulipchat.com/#narrow/channel/266220-t-rustdoc/topic/rustdoc_json.20ideas/with/524453896) [more](https://rust-lang.zulipchat.com/#narrow/channel/266220-t-rustdoc/topic/rustdoc-json.3A.20compressed.20output/with/462869885) [speed](https://rust-lang.zulipchat.com/#narrow/channel/266220-t-rustdoc/topic/.28De.29serialization.20speed.20of.20JSON.20docs), and smaller docs. There are proposals to make the JSON smaller (and therefor faster) by making field-names shorter, and omitting them when the value is the default. But ## How good is it? In a [very unscientific benchmark](https://github.com/aDotInTheVoid/rustdocjson-encoding-bench) for aws-sdk-ec2, it's ~3.6x smaller (255MiB vs 69 MiB) and ~1.8x faster to deserialize (1.6273 s vs 914.05 ms) ## What's the metaformat - 21 bytes of magic numbers - varint(u32) format version - `Crate` as usual This way, users can look at the magic number to check it's a rustdoc-json-postcard file, then read the version number to know if they can decode it. Only then can they deserialize the `Crate` itself. I plan to write a library that does this, so it's easy to do well. ## What about the name Right now, this means that the `rustdoc::json` module produces postcard & json output. And the overall feature is still called "rustdoc json". There should probably be a unified name for the both of these. Maybe something like "Rustdoc IR Output"? But that can wait for a follow-up commit I think.
I think we should bump whenever either of them requires a bump. There are cases where only JSON is broken (field/varient rename), and where only postcard is broken (field/varient reorder). But I don't think the gains from seperate versioning (wider compatibilty ranges) are worth the costs (more complex implementation, harder to explain to contributors which format versions need to be bumped, harder to explain to tool writers). |
View all comments
What
rustdoc --output-format=postcardis like rustdoc-json, but usinghttps://postcard.rs/ / https://docs.rs/postcard/1.1.1/ instead of JSON.
Why
JSON Size and speed isn't great. People
want
more
speed,
and smaller docs. There are proposals to make the JSON smaller (and therefor
faster) by making field-names shorter, and omitting them when the value is the
default. But
How good is it?
In a very unscientific
benchmark for
aws-sdk-ec2, it's ~3.6x smaller (255MiB vs 69 MiB) and ~1.8x faster to
deserialize (1.6273 s vs 914.05 ms)
What's the metaformat
Crateas usualThis way, users can look at the magic number to check it's a
rustdoc-json-postcard file, then read the version number to know if they can
decode it. Only then can they deserialize the
Crateitself. I plan to write alibrary that does this, so it's easy to do well.
What about the name
Right now, this means that the
rustdoc::jsonmodule produces postcard & jsonoutput. And the overall feature is still called "rustdoc json". There should
probably be a unified name for the both of these.
Maybe something like "Rustdoc IR Output"? But that can wait for a follow-up
commit I think.
Why is this a draftHtmlRendererandJsonRendererare configures from the same options, we should change thisRenderOptionstoDocContext#147832I want to make it more principled how rustdoc before the format inspects.is_json()instead of the current hacksCompiletest changes should be spun into their own thingDocs